package library.net.retrofit.function;

import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.BiFunction;
import io.reactivex.functions.Function;

/**
 * retry when network exception
 *
 * @author Created by WZG on 2016/10/17.
 * @version 1.0
 */
public class RxRetryFunction implements Function<Observable<? extends Throwable>, Observable<?>> {
    /**
     * retry次数
     */
    private int count = 3;
    /**
     * 基础延迟（第一次retry延迟）
     */
    private long delay = 3000;
    /**
     * 每次retry叠加延迟（第n次retry延迟 = 基础延迟 + increaseDelay * （n - 1））
     */
    private long increaseDelay = 3000;

    public RxRetryFunction() {

    }

    /**
     * @param count retry count
     * @param delay retry base delay
     */
    public RxRetryFunction(int count, long delay) {
        this.count = count;
        this.delay = delay;
    }

    /**
     * @param count         retry count
     * @param delay         retry base delay
     * @param increaseDelay retry increaseDelay
     */
    public RxRetryFunction(int count, long delay, long increaseDelay) {
        this.count = count;
        this.delay = delay;
        this.increaseDelay = increaseDelay;
    }

    @Override
    public Observable<?> apply(@NonNull Observable<? extends Throwable> observable) {
        return observable
                .zipWith(Observable.range(1, count + 1), new BiFunction<Throwable, Integer, RetryWrapper>() {
                    @Override
                    public RetryWrapper apply(@NonNull Throwable throwable, @NonNull Integer integer) {
                        return new RetryWrapper(throwable, integer);
                    }
                }).flatMap(new Function<RetryWrapper, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(@NonNull RetryWrapper wrapper) {
                        if ((wrapper.throwable instanceof ConnectException
                                || wrapper.throwable instanceof SocketTimeoutException
                                || wrapper.throwable instanceof TimeoutException)
                                && wrapper.index < count + 1) { // 如果超出重试次数也抛出错误，否则默认是会进入onCompleted
                            return Observable.timer(delay + (wrapper.index - 1) * increaseDelay, TimeUnit.MILLISECONDS);
                        }
                        return Observable.error(wrapper.throwable);
                    }
                });
    }

    /**
     *
     */
    private class RetryWrapper {
        private int index;
        private Throwable throwable;

        public RetryWrapper(Throwable throwable, int index) {
            this.index = index;
            this.throwable = throwable;
        }
    }
}
